package com.cntaiping.workutil.videoanalysis;

import com.cntaiping.analysis.domain.VideoAnalysisDto;
import com.cntaiping.analysis.domain.VideoAnalysisResult;
import com.cntaiping.analysis.domain.VideoAnalysisScope;
import com.cntaiping.analysis.service.AnalysisCsvService;
import com.cntaiping.analysis.service.AnalysisVideoMoveService;
import com.cntaiping.analysis.service.AnalysisVideoTempService;
import com.cntaiping.analysis.service.DataAnalysisService;
import com.cntaiping.analysis.utils.CsvUtils;
import com.cntaiping.analysis.utils.JsonUtil;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

@Service
public class DataAnalysisServiceImpl {
    private static final Logger logger = LoggerFactory.getLogger(DataAnalysisServiceImpl.class);

    @Autowired
    private VideoAnalysisScope videoAnalysisScope;

    @Autowired
    private ThreadPoolTaskExecutor executor;

    @Autowired
    private AnalysisVideoMoveService moveService;

    @Autowired
    private AnalysisVideoTempService tempService;

    @Autowired
    private AnalysisCsvService analysisCsv;

    @Value("${video.path.minutes1-csv}")
    private String minutes1;
    @Value("${video.path.minutes5-csv}")
    private String minutes5;
    @Value("${video.path.minutes30-csv}")
    private String minutes30;
    @Value("${video.path.data30-csv}")
    private String data30;
    @Value("${video.path.work-dir}")
    private String workdir;

    private final String pattern = "yyyy-MM-dd HH:mm:ss";

    @Override
    public List<VideoAnalysisResult> analysisVideoData(List<VideoAnalysisDto> videos, VideoAnalysisScope analysisScope) {
        List<VideoAnalysisResult> resultList = new ArrayList<>();
        List<VideoAnalysisScope.Analysis> config;
        List<VideoAnalysisScope.Analysis> analyses = analysisScope.getAnalysis();
        if (analyses != null && analyses.size() > 0) {
            config = analyses;
            logger.info("use request parameters config:{}", JsonUtil.getFormatJson(config));
            analyses.forEach(item -> {
                if (StringUtils.isAnyBlank(item.getCount1(), item.getCount2(), item.getCount3(), item.getType())) {
                    throw new RuntimeException("parameters config error,please check whether parameters is empty");
                }
            });
        } else {
            config = videoAnalysisScope.getAnalysis();
            for (int i = 0; i < config.size(); i++) {
                logger.info("use default config[{}]:{}", i + 1, config.get(i));
            }
        }
        try {

            /**
             * key = type  value = 同一类型的数据
             */
            Map<String, List<VideoAnalysisDto>> typeMap = videos.stream().sorted(Comparator.comparing(VideoAnalysisDto::getType)).collect(Collectors.groupingBy(VideoAnalysisDto::getType));

            List<Callable<Map<String, List<VideoAnalysisResult>>>> analysisList = new ArrayList<>();

            for (String type : typeMap.keySet()) {
                logger.info("*****type:[{}]has match data[{}]", type, typeMap.get(type).size());

                /**
                 * 没有配置动作类型规则的数据跳过
                 */
                List<VideoAnalysisScope.Analysis> list = config.stream().filter(scope -> StringUtils.equals(type, scope.getType())).collect(Collectors.toList());
                VideoAnalysisScope.Analysis analysisConfig;
                if (list.size() > 0) {
                    analysisConfig = list.get(0);
                } else {
                    logger.error("*****type:[{}}No monitoring time rule is configured", type);
                    continue;
                }

                analysisList.add(() -> analysisSeatData(type, typeMap.get(type), resultList, analysisConfig,analysisScope.getJobId()));

            }
            /**
             * 统一移动图片
             */
            List<Future<Map<String, List<VideoAnalysisResult>>>> futures = executor.getThreadPoolExecutor().invokeAll(analysisList);

            /**
             * 动作类型检测结束,开始移动图片
             */
            moveService.analysisResult(futures,videos,analysisScope.getJobId());

            /**
             * 生成只带有符合30分钟规则的csv
             */
            List<VideoAnalysisResult> collect = resultList.stream().filter(result -> result.getNum30() > 0).collect(Collectors.toList());
            if(collect.size()>0){
                String path = workdir + "/" + analysisScope.getJobId() + data30;
                analysisCsv.analysisCsv(collect, path, "num30");
            }
            /**
             * 生成9,10csv
             */
            tempService.generateDateScope(analysisScope.getJobId());
        } catch (Exception e) {
            logger.error("Analyze data errors based on type!");
            e.printStackTrace();
            throw new RuntimeException("Analyze data errors based on type");
        }

        return resultList;
    }

    public Map<String, List<VideoAnalysisResult>> analysisSeatData(String type, List<VideoAnalysisDto> videoAnalysisDtoList, List<VideoAnalysisResult> resultList, VideoAnalysisScope.Analysis analysisConfig,String jobId) {
        Map<String, List<VideoAnalysisResult>> result = new HashMap<>();
        try {

            /**
             * key = seat value=同一类型,同一座位下的数据
             */
            Map<String, List<VideoAnalysisDto>> seatMap = videoAnalysisDtoList.stream().sorted(Comparator.comparing(VideoAnalysisDto::getSeat)).collect(Collectors.groupingBy(VideoAnalysisDto::getSeat));

            VideoAnalysisScope.AnalysisConfig min1 = new VideoAnalysisScope.AnalysisConfig("1", analysisConfig.getScope1(), analysisConfig.getCount1());

            VideoAnalysisScope.AnalysisConfig min5 = new VideoAnalysisScope.AnalysisConfig("5", analysisConfig.getScope2(), analysisConfig.getCount2());

            VideoAnalysisScope.AnalysisConfig min30 = new VideoAnalysisScope.AnalysisConfig("30", analysisConfig.getScope3(), analysisConfig.getCount3());
            /**
             * 每个类型-每个分析阶段匹配人数收集生成csv
             */
            List<VideoAnalysisResult> min1List = new ArrayList<>();
            List<VideoAnalysisResult> min2List = new ArrayList<>();
            List<VideoAnalysisResult> min3List = new ArrayList<>();


            for (String seat : seatMap.keySet()) {
                /**
                 * 一个时间段内一个人同一类型的所有数据
                 */
                List<VideoAnalysisDto> videoAnalysisDto = seatMap.get(seat);
                logger.info("type:[{}},seat:[{}]has match data[{}]", type, seat, seatMap.get(seat).size());
                if("jf".equals(seat)){
                    logger.info("Pictures in the data center are not involved in data analysis!");
                    continue;
                }
                /**
                 * 根据时间排序
                 */
                videoAnalysisDto.sort(Comparator.comparing(VideoAnalysisDto::getAnalysisTime));
                /**
                 * 第一阶段检测
                 * 最终报表，原始分析数据，开始时间，配置1  ->  筛选条件（如一分钟7条）
                 * 最终报表数据+1，返回阶段1过程表   满足1，满足2，满足3（时间begin-时间end）
                 */
                List<VideoAnalysisResult> oneMinutesResult = null;
                if (videoAnalysisDto.size() >= Integer.parseInt(min1.getCount())) {
                    oneMinutesResult = checkVideo(resultList, videoAnalysisDto, videoAnalysisDto.get(0).getAnalysisTime(), min1);
                }
                if (oneMinutesResult != null && oneMinutesResult.size() > 0) {
                    min1List.addAll(oneMinutesResult);
                    logger.info("[{}]one minute check result,Number of satisfactions：{}", oneMinutesResult.get(0).getSeat(), oneMinutesResult.size());
                    /**
                     * 第二阶段检测
                     *
                     */
                    if (oneMinutesResult.size() >= Integer.parseInt(min5.getCount())) {
                        List<VideoAnalysisResult> fiveMinutesResult = checkOneMinute(resultList, oneMinutesResult, min5);
                        if (fiveMinutesResult.size() > 0) {
                            min2List.addAll(fiveMinutesResult);
                            logger.info("[{}]five minutes check result,Number of satisfactions：{}", fiveMinutesResult.get(0).getSeat(), fiveMinutesResult.size());

                            /**
                             * 第三阶段检测
                             */
                            if (fiveMinutesResult.size() >= Integer.parseInt(min30.getCount())) {
                                List<VideoAnalysisResult> thirtyMinutesResult = checkOneMinute(resultList, fiveMinutesResult, min30);
                                if (thirtyMinutesResult.size() > 0) {
                                    min3List.addAll(thirtyMinutesResult);
                                    logger.info("[{}]thirty minutes check result,Number of satisfactions：{},\n{}", thirtyMinutesResult.get(0).getSeat(), thirtyMinutesResult.size(), JsonUtil.getFormatJson(thirtyMinutesResult));

                                    result.put(type + "_" + thirtyMinutesResult.get(0).getSeat(), thirtyMinutesResult);

                                }
                            }
                        }
                    }

                }



            }

            logger.info("Start generating CSV of the analysis interval data!");
            /**
             * 1分钟
             */
            if(min1List.size()>0){
                String path1 = workdir + "/" + jobId + minutes1;
                analysisCsv.analysisCsv(min1List, path1, "num");
            }else {
                logger.info("*****type[{}]No satisfying data items exist*****",type);
            }

            /**
             * 5分钟
             */
            if(min2List.size()>0){
                String path5 = workdir + "/" + jobId + minutes5;
                analysisCsv.analysisCsv(min2List, path5, "num5");
            }
            /**
             * 30分钟
             */
            if(min3List.size()>0){
                String path30 = workdir + "/" + jobId + minutes30;
                analysisCsv.analysisCsv(min3List, path30, "num30");
            }
        } catch (Exception e) {
            logger.error("Errors in the analysis data based on seats!");
            e.printStackTrace();
            throw new RuntimeException("Errors in the analysis data based on seats");

        }
        return result;


    }



    private List<VideoAnalysisResult> checkOneMinute(List<VideoAnalysisResult> resultList, List<VideoAnalysisResult> minuteReq, VideoAnalysisScope.AnalysisConfig config) {
        List<VideoAnalysisResult> minutesResult = new ArrayList<>();
        minuteReq.sort(Comparator.comparing(VideoAnalysisResult::getEndTime));

        String[] begin = {minuteReq.get(0).getStartTime()};

        List<VideoAnalysisResult> first = minuteReq.stream().filter(one -> compareStr(begin[0], one.getEndTime()) == -1).collect(Collectors.toList());

        /**
         * 排序后结束时间都比第一个开始时间大
         */
        while (first.size() >= Integer.parseInt(config.getCount())) {

            Date end = DateUtils.addSeconds(StrDate(begin[0]), Integer.parseInt(config.getScope()));

            Predicate<VideoAnalysisResult> predicate = one -> (compareStr(begin[0], one.getStartTime()) == 0 || compareStr(begin[0], one.getStartTime()) == -1)
                    && (compareDate(StrDate(one.getEndTime()), end) == 0 || compareDate(StrDate(one.getEndTime()), end) == -1);

            List<VideoAnalysisResult> matchList = minuteReq.stream().filter(predicate).collect(Collectors.toList());

            String nextBegin;

            if (matchList.size() < Integer.parseInt(config.getCount())) {
                nextBegin = first.get(1).getStartTime();
            } else {
                VideoAnalysisResult result = first.get(0);

                VideoAnalysisResult min5Data = null;

                List<VideoAnalysisResult> nextOne = minuteReq.stream().filter(one -> compareDate(end, StrDate(one.getStartTime())) == -1).collect(Collectors.toList());

                List<VideoAnalysisResult> resultAnalysis = resultList.stream().filter(one -> StringUtils.equals(result.getType(), one.getType()) && StringUtils.equals(result.getSeat(), one.getSeat())).collect(Collectors.toList());

                if (resultAnalysis.size() > 0) {
                    if ("5".equals(config.getLevel())) {
                        resultAnalysis.get(0).setNum5(resultAnalysis.get(0).getNum5() + 1);
                        min5Data = new VideoAnalysisResult(result.getType(), result.getName(), result.getSeat(), begin[0], DateFormatUtils.format(end, pattern), 0, matchList.size(), 0);
                    } else if ("30".equals(config.getLevel())) {
                        resultAnalysis.get(0).setNum30(resultAnalysis.get(0).getNum30() + 1);
                        min5Data = new VideoAnalysisResult(result.getType(), result.getName(), result.getSeat(), begin[0], DateFormatUtils.format(end, pattern), 0, 0, matchList.size());
                    }
                    resultAnalysis.get(0).setEndTime(DateFormatUtils.format(end, pattern));
                } else {
                    throw new RuntimeException("The analysis result is error!");
                }
                minutesResult.add(min5Data);
                if (nextOne.size() > 0) {
                    nextOne.sort(Comparator.comparing(VideoAnalysisResult::getEndTime));
                    nextBegin = nextOne.get(0).getStartTime();
                } else {
                    break;
                }

            }

            first = minuteReq.stream().filter(one -> compareStr(nextBegin, one.getEndTime()) == -1).collect(Collectors.toList());
            begin[0] = nextBegin;


        }


        return minutesResult;
    }

    /**
     * while循环
     *
     * @param resultList
     * @param analysisList
     * @param begin
     * @param config
     */
    public List<VideoAnalysisResult> checkVideo(List<VideoAnalysisResult> resultList, List<VideoAnalysisDto> analysisList, String begin, VideoAnalysisScope.AnalysisConfig config) {

        List<VideoAnalysisResult> oneMinutesResult = new ArrayList<>();
        /**
         * 走到这里数据至少有一条,递归出口:分析数据量小于目标数据量
         */
        String[] start = {begin};
        List<VideoAnalysisDto> bigAnalysis = analysisList.stream().filter(analysis -> compareStr(start[0], analysis.getAnalysisTime()) == -1).collect(Collectors.toList());

        while (bigAnalysis.size() >= Integer.parseInt(config.getCount()) - 1) {

            /**
             * 数据量>目标数据 开始时间+持续时长  之后
             * 1)达标   开始时间推移到 开始时间+持续时长 后 开始新一轮
             * 2)不达标 开始时间为当前时间的 下一条数据的时间 开始下一轮
             * 直到 数据量<目标数量  结束
             */
            Date end = DateUtils.addSeconds(StrDate(start[0]), Integer.parseInt(config.getScope()));
            /**
             * 有数据
             */

            /**
             * >=begin,<=end 的数据量
             */
            Predicate<VideoAnalysisDto> predicate = analysis -> (compareStr(start[0], analysis.getAnalysisTime()) == -1 || compareStr(start[0], analysis.getAnalysisTime()) == 0)
                    && (compareDate(StrDate(analysis.getAnalysisTime()), end) == -1 || compareDate(StrDate(analysis.getAnalysisTime()), end) == 0);
            List<VideoAnalysisDto> scopeAnalysis = analysisList.stream().filter(predicate).collect(Collectors.toList());
            String nextBegin;
            if (scopeAnalysis.size() < Integer.parseInt(config.getCount())) {
                /**
                 * 不达标,起始时间为下一条数据
                 */
                bigAnalysis.sort(Comparator.comparing(VideoAnalysisDto::getAnalysisTime));
                nextBegin = bigAnalysis.get(0).getAnalysisTime();


            } else {
                List<VideoAnalysisDto> midAnalysis = analysisList.stream().filter(analysis -> compareDate(end, StrDate(analysis.getAnalysisTime())) == -1).collect(Collectors.toList());
                /**
                 * 达标,起始时间为增加scope后的下一条数据
                 */

                /**
                 * 有可能再次达标,次数+1
                 */
                VideoAnalysisDto videoAnalysisDto = analysisList.get(0);
                List<VideoAnalysisResult> collect = resultList.stream().filter(item -> StringUtils.equals(item.getType(), videoAnalysisDto.getType())
                        && StringUtils.equals(item.getSeat(), videoAnalysisDto.getSeat())).collect(Collectors.toList());
                VideoAnalysisResult oneResult = new VideoAnalysisResult(videoAnalysisDto.getType(), videoAnalysisDto.getName(), videoAnalysisDto.getSeat(),
                        start[0], DateFormatUtils.format(end, pattern), scopeAnalysis.size(), 0, 0);

                if (collect.size() > 0) {
                    collect.get(0).setNum(collect.get(0).getNum() + 1);
                    collect.get(0).setEndTime(DateFormatUtils.format(end, pattern));
//                    logger.info("[{}}满足次数:[{}],区间包含数据量:[{}},开始时间:{},结束时间:{}", analysisList.get(0).getSeat(), collect.get(0).getNum(), scopeAnalysis.size(), start[0], DateFormatUtils.format(end, pattern));
                } else {
                    VideoAnalysisResult result = new VideoAnalysisResult(videoAnalysisDto.getType(), videoAnalysisDto.getName(), videoAnalysisDto.getSeat(),
                            start[0], DateFormatUtils.format(end, pattern), 1, 0, 0);
                    resultList.add(result);
//                    logger.info("[{}]首次满足条件,区间包含数据量:[{}},开始时间:{},结束时间:{}", analysisList.get(0).getSeat(),scopeAnalysis.size(), start[0], DateFormatUtils.format(end, pattern));

                }
                oneMinutesResult.add(oneResult);
                if (midAnalysis.size() > 0) {
                    midAnalysis.sort(Comparator.comparing(VideoAnalysisDto::getAnalysisTime));
                    nextBegin = midAnalysis.get(0).getAnalysisTime();
                } else {
                    break;
                }

            }


            bigAnalysis = analysisList.stream().filter(analysis -> compareStr(nextBegin, analysis.getAnalysisTime()) == -1).collect(Collectors.toList());
            start[0] = nextBegin;

        }

        return oneMinutesResult;


    }

    /**
     * 递归实现
     *
     * @param resultList
     * @param analysisList
     * @param begin
     * @param config
     */
    public void recursionCheck(List<VideoAnalysisResult> resultList, List<VideoAnalysisDto> analysisList, String begin, VideoAnalysisScope.AnalysisConfig config) {
        /**
         * 走到这里数据至少有一条,递归出口:分析数据量小于目标数据量
         */
        List<VideoAnalysisDto> bigAnalysis = analysisList.stream().filter(analysis -> compareStr(begin, analysis.getAnalysisTime()) == -1).collect(Collectors.toList());
        if (bigAnalysis.size() >= Integer.parseInt(config.getCount()) - 1) {
            /**
             * 数据量>目标数据 开始时间+持续时长  之后  1.没有数据 满足一次要求 结束  2.仍有数据 判断是否达标
             * 1)达标   开始时间推移到 开始时间+持续时长 后 开始新一轮递归
             * 2)不达标 开始时间为当前时间的 下一条数据的时间 开始下一轮递归
             * 直到 数据量<目标数量  递归结束
             */
            Date end = DateUtils.addSeconds(StrDate(begin), Integer.parseInt(config.getScope()));
            List<VideoAnalysisDto> midAnalysis = analysisList.stream().filter(analysis -> compareDate(end, StrDate(analysis.getAnalysisTime())) == -1).collect(Collectors.toList());
            if (midAnalysis.size() > 0) {
                /**
                 * 有数据
                 */

                /**
                 * >=begin,<=end 的数据量
                 */
                Predicate<VideoAnalysisDto> predicate = analysis -> (compareStr(begin, analysis.getAnalysisTime()) == -1 || compareStr(begin, analysis.getAnalysisTime()) == 0)
                        && (compareDate(StrDate(analysis.getAnalysisTime()), end) == -1 || compareDate(StrDate(analysis.getAnalysisTime()), end) == 0);
                List<VideoAnalysisDto> scopeAnalysis = analysisList.stream().filter(predicate).collect(Collectors.toList());
                String nextBegin;
                boolean flag = false;
                if (scopeAnalysis.size() < Integer.parseInt(config.getCount())) {
                    /**
                     * 不达标,起始时间为下一条数据
                     */
                    bigAnalysis.sort(Comparator.comparing(VideoAnalysisDto::getAnalysisTime));
                    nextBegin = bigAnalysis.get(0).getAnalysisTime();


                } else {
                    /**
                     * 达标,起始时间为增加scope后的下一条数据
                     */
                    flag = true;
                    midAnalysis.sort(Comparator.comparing(VideoAnalysisDto::getAnalysisTime));
                    nextBegin = midAnalysis.get(0).getAnalysisTime();
                    /**
                     * 有可能再次达标,次数+1
                     */
                    VideoAnalysisDto videoAnalysisDto = analysisList.get(0);
                    List<VideoAnalysisResult> collect = resultList.stream().filter(item -> StringUtils.equals(item.getType(), videoAnalysisDto.getType())
                            && StringUtils.equals(item.getSeat(), videoAnalysisDto.getSeat())).collect(Collectors.toList());
                    if (collect.size() > 0) {
                        collect.get(0).setNum(collect.get(0).getNum() + 1);
                        collect.get(0).setEndTime(DateFormatUtils.format(end, pattern));
                        logger.info("[{}}满足次数:[{}],开始时间:{},结束时间:{}", analysisList.get(0).getSeat(), collect.get(0).getName(), begin, DateFormatUtils.format(end, pattern));
                    } else {
                        VideoAnalysisResult result = new VideoAnalysisResult(videoAnalysisDto.getType(), videoAnalysisDto.getName(), videoAnalysisDto.getSeat(),
                                begin, DateFormatUtils.format(end, pattern), 1, 0, 0);
                        resultList.add(result);
                        logger.info("[{}]首次满足条件,开始时间:{},结束时间:{}", analysisList.get(0).getSeat(), begin, DateFormatUtils.format(end, pattern));


                    }


                }
                if (flag)
                    logger.info("下一次开始时间:{},size:[{}}", nextBegin, scopeAnalysis.size());

                recursionCheck(resultList, analysisList, nextBegin, config);

            } else {
                /**
                 * 后续没有数据刚好够满足一次条件
                 */
                logger.info("[{}]首次满足条件,开始时间:{},结束时间:{}", analysisList.get(0).getSeat(), begin, DateFormatUtils.format(end, pattern));
                VideoAnalysisResult result = new VideoAnalysisResult();
                result.setType(analysisList.get(0).getType());
                result.setSeat(analysisList.get(0).getSeat());
                result.setName(analysisList.get(0).getName());
                result.setStartTime(begin);
                result.setEndTime(DateFormatUtils.format(end, pattern));
                result.setNum(1);
                resultList.add(result);

            }

        }
    }

    public Date StrDate(String time) {
        try {
            String pattern = "yyyy-MM-dd HH:mm:ss";
            return DateUtils.parseDate(time, pattern);
        } catch (Exception e) {
            logger.error("data parse error!");
            e.printStackTrace();
            throw new RuntimeException("data parse error");
        }


    }

    public int compareStr(String time1, String time2) {
        /**
         * 字符串时间比较
         * time1<time2: -1  time1=time2: 0  time1>time2: 1
         */
        return DateUtils.truncatedCompareTo(StrDate(time1), StrDate(time2), Calendar.SECOND);

    }

    public int compareDate(Date time1, Date time2) {
        return DateUtils.truncatedCompareTo(time1, time2, Calendar.SECOND);
    }



}
